假設一名學生正在解一道答案固定的題目。大部分時間,他都能寫完;某一次卻不是答案被判錯,而是監考老師在時間到時直接收走尚未完成的考卷。成績單上仍然只有一個紅色的 FAIL,但它可能代表學生不會、題目有問題,也可能是計時與考場出了狀況。
timeout(逾時)也是如此:測試還沒完成,runner 就因超過時間上限而終止它。這不自動等於產品邏輯錯誤,卻也不能因為「可能只是環境」就忽略。今天的難題不是如何把紅燈快速變綠,而是如何分辨究竟是產品、測試,還是執行測試的環境出了問題。
我正在製作 ARCI(Autonomous Resilient Communication Intelligence),希望系統在網路、供應商、資料品質或執行結果不穩定時,仍能在明確界線內觀察、選擇策略、執行、驗證,必要時重新規劃。現在處理的 Layer 4 名為 Adaptive Learning Governance:它接收上一層留下的決策與結果紀錄,在離線、可重播的條件下整理經驗、提出有限候選,再經明確批准才切換本機參考策略。[6]
這個名稱很容易讓人聯想到「AI 自己進化」,但目前的範圍不是那件事。它不改寫程式、不從正式流量自行學習,也不負責部署;它是一套本機、deterministic(相同輸入應得到相同結果)的 reference foundation,用來先把治理契約做清楚。[6]
前一天,我才剛遇到局部測試全過,回歸測試仍拒絕封存的情況。這次新的 timeout 又提醒我:要證明 deterministic,不只要看最後的 assertion(斷言)答對沒有,也要讓測試有合理而有界的時間完成。
最早的三個 replay 測試沿用五秒預算;當資料量、整套 suite 的負載與 worker 排程疊在一起,它們可能還在計算就先被停止。這輪候選只把三個測試各自的時間上限調成十或十五秒。loop 次數、fixtures、輸入、assertions、內容指紋與 production code 都沒有改;拿掉 timeout 數字後,修改前後內容相同。[1][4]
所以我能很精確地說:我延長的是「等它回答的時間」,不是「降低答對的標準」。但這只是對三個具名測試的證明,不能外推成整個測試系統已經可靠。
修補後的 focused qualification(針對受影響範圍的局部驗收)是 218/218 通過;規定的 anti-flake qualification 也完成 20/20。flaky test 指的是程式與輸入沒有相應變動,同一個測試卻有時通過、有時失敗。連續通過能增加信心,仍不能取代下一層關鍵回歸測試。[1]
critical regression(關鍵回歸測試)會確認新修改沒有破壞既有承諾。第一次正式執行共有 80 個測試檔、1,539 項測試;75 個檔案通過,5 個失敗,測試項目是 1,534/1,539 通過,沒有跳過。五項失敗全是 timeout,runner 另留下三次 worker RPC timeout;原本調整預算的三組測試反而都在這次執行中通過。[1][2]
1,534 ÷ 1,539 約為 99.7%,看起來很接近滿分。但 qualification 不是六十分及格的考試:有些 gate 必須完整成立。對這一輪可靠性驗收而言,99.7% 通過和「已證明可靠」不是同一件事。
當時的證據只能說,問題範圍已不再像三個獨立 timeout 那麼窄,較符合整體 runner capacity、worker scheduling 或資源競爭的方向;它還不能證明 Vitest 本身有 bug。資料庫、race condition、共享狀態與非決定性順序都是 flaky test 的一般可能原因,也不能在沒有觀測之前套到這次事件上。
最有誘惑力的動作是再輸入一次測試命令。假如第二次變綠,就把第一次紅燈叫作偶發事件,繼續封存。但若驗收規則是「失敗就重跑,直到挑到一次成功」,最後證明的只是我重跑得夠多次,不是系統可重現地可靠。
因此那次正式 qualification 保留第一輪失敗並立即 STOP。fail-closed 的意思是:必要條件不成立或原因不明時,先拒絕往下走。當下 full regression、靜態與安全關卡、commit、push、Layer 4 seal 全部沒有執行,Layer 5 也沒有開始。[1]
這不表示第一次結果必然揭露唯一真相。它表示失敗必須先成為調查對象;後來可以做隔離量測、固定工作量的比較與正式的新驗收,但不能用一次運氣較好的重跑覆蓋原紀錄。
測試量變大後,tests 本身就是軟體系統:它有 runner、workers、記憶體、排程、timeout、fixtures、資料庫、生命週期、清理與 orchestration。Vitest worker 是 runner 用來分配測試檔的工作程序;如果多個高 CPU 工作同時搶資源,測試的牆鐘時間與回報結果的通道都可能被拖慢。
我不只是在用測試驗證 ARCI,也必須驗證「驗證 ARCI 的那套系統」是否可靠。這正是第一次紅燈值得保留的理由:五個不同檔案一起逾時,再伴隨 worker 回報逾時,已經是一組需要解釋的分布,不是一個適合刪掉的雜訊點。
flowchart TD
A[Replay timeout] --> B[檢查 workload 與 assertions]
B --> C[Test-local budget]
C --> D[Focused 與 anti-flake 通過]
D --> E[Critical regression 失敗]
E --> F[STOP,不以重跑覆蓋]
F --> G[隔離量測與 worker A/B 比較]
G --> H[受控修復]
H --> I[Pre/Post qualification]
I --> J[Layer 4 seal]
J --> K[Layer 5 僅獲准下一步]
後續工作不是重跑同一條命令碰運氣,而是分開量測五個失敗工作、比較 worker 數量、觀察主機資源,並檢查資料庫活動與鎖等待。隔離樣本通常遠快於失敗的整套執行;兩個 workers 比四個更穩定,資料庫也沒有活動、閒置交易或鎖等待可解釋那些逾時。證據最後把根因收斂為跨 suite 的 CPU 資源競爭與 worker starvation(工作程序長時間拿不到足夠資源)。[2][3]
修復因此分成兩部分:最多使用兩個 workers,先把十一個量測出的高 CPU/replay/timeout 檔案放進單一隔離群組;另外只為有觀測依據的測試設定有限的本地時間預算。沒有修改 production code,四個 timeout 測試的工作量與斷言也保持不變。[3][4]
接著才執行新的正式資格流程。受影響群組 273/273 通過,固定工作量 anti-flake 連續五輪通過;commit 前後的 critical regression 都是 1,539/1,539,full regression 都是 2,152/2,152,沒有跳過項目或 worker RPC timeout。靜態檢查、安全檢查、舊 Layer 保存、commit 後複驗與遠端一致性也全部通過,Layer 4 才在新的提交上完成 seal。[5]
最新 verdict 已從當時的 ARCI_LAYER4_BLOCKED_TEST_INFRASTRUCTURE 更新為 ARCI_LAYER4_READY。這不是回頭把第一次 FAIL 改成 PASS,而是保留它、證明根因、做有界修復,再跑一套新的 pre-commit 與 post-commit qualification。[1][5]
Layer 5 仍是 NOT_STARTED,目前只有「允許成為下一步」。樓下驗收完成,代表可以開始規劃樓上,不代表樓上已經蓋好。Layer 4 的 READY 也只證明這套本機、離線、reference foundation 通過既定工程 gate,不是正式環境、自主學習成效或真實災害情境的證明。[5][6]
這一天最值得留下的不是綠燈,而是兩個不同時間點都沒有被混寫:第一次 critical regression 失敗時,我沒有靠重跑取得 seal;後來證據足夠時,我也沒有把 READY 擴張成 Layer 5 已完成。可靠的驗收要能說「現在不能繼續」,也要能在原因真的被證明後,清楚說明為什麼現在可以往下一步走。